# -*- coding: utf-8 -*-
"""
UTc! module: sandbox

Purpose
=======
 Provides an interface that can be used to test interactions with objects using
 a subset of UTc!'s complete engine.
 
Legal
=====
 This code is based on, but is increasingly dissimilar to, the LGPL code found
 at http://www.pygame.org/wiki/OBJFileLoader on February 9th, 2009.
 
 All other code, unless otherwise indicated, is original, and subject to the
 terms of the GPLv2, which is provided in COPYING.
 
 (C) Neil Tallim, 2009
"""
import math
import ode
import OpenGL
import OpenGL.GL as GL
import OpenGL.GLU as GLU
import optparse
import os
import pygame
import random
import sys

import src.camera as camera
import src.collisions as collisions
import src.constants as constants
import src.effects as effects
import src.gameobjects as gameobjects
import src.hud as hud
import src.obstacles as obstacles
import src.path_utils as path_utils
import src.session_constants as session_constants
import src.tankchan as tankchan
import src.weapons as weapons

import src.config.parser

import src.rendering.rendering as rendering
import src.rendering.shading as shading

import src.sandbox.generic_rendering as generic_rendering
import src.sandbox.ode_rendering as ode_rendering

def _chooseTankChan(model_number=None):
	"""
	Presents the user with a menu that allows them to choose one of the game's
	ships for testing.
	
	@type model_number: int|None
	@param model_number: If specified, this will automatically be mapped as an
	    index value against the list of ships available, skipping the menu stage.
	
	@rtype: str
	@return: The path to the chosen ship's descriptor file.
	"""
	model_path = path_utils.getObjectsPath()
	parser = src.config.parser.Parser(model_path + "ships.xml")
	
	available_ships = []
	for ship in parser.getCollection(None, 'ships', 'ship'):
		available_ships.append((
		 parser.readData(ship, 'descriptor').replace('/', os.sep),
		 parser.readData(ship, 'identification.name'),
		 parser.readData(ship, 'identification.model-number')
		))
		
	if model_number is None:
		for (i, (descriptor, name, model)) in enumerate(available_ships):
			print '%i) "%s" (%s)' % (i, name, model)
		print "Enter the number of the ship to be loaded"
		model_number = int(raw_input())
	return model_path + available_ships[model_number][0]
	
def _determineRenderMode(options):
	"""
	Evaluates the user's chosen render mode against their OpenGL API and
	determines whether the mode should be kept or reverted to basic flat-shading.
	
	@param options: A collection of parsed options, as returned by
	    C{optparse.OptionParser.parse_args()}.
	
	@rtype: int
	@return: A render mode constant, specifying the type of rendering that should
	    be applied.
	"""
	render_mode = None
	if options.wireframe:
		render_mode = shading.RENDER_MODE_WIREFRAME
	elif options.plain:
		render_mode = shading.RENDER_MODE_BASIC
	else:
		if options.gradient:
			render_mode = shading.RENDER_MODE_GRADIENT
		elif options.flat:
			render_mode = shading.RENDER_MODE_FLAT
		else:
			render_mode = shading.RENDER_MODE_CEL
			
		try:
			shading.init()
		except AttributeError:
			sys.stderr.write("No valid shading API found; switching to basic mode.\n")
			render_mode = shading.RENDER_MODE_BASIC
	return render_mode
	
def _updateBackgroundColour(current_colour, target_colour):
	"""
	Updates the current pastel to use as the background colour while moving
	slowly towards the target. When the target has been reached, a new target
	pastel is chosen.
	
	@type current_colour: sequence(3)
	@param current_colour: The current background colour's (r, g, b) values.
	@type target_colour: sequence(3)
	@param target_colour: The target background colour's (r, g, b) values.
	
	@rtype: tuple(2)
	@return: The current background colour's (r, g, b) and the target background
	    colour's (r, g, b) values.
	"""
	if not [None for (c, t) in zip(current_colour, target_colour) if abs(t - c) > 0.0005]: #Choose a new pastel target.
		target_colour = [random.uniform(0.5, 0.95) for i in range(3)]
	current_colour = [c + (cmp(t - c, 0) * 0.0005 * random.random()) for (c, t) in zip(current_colour, target_colour)] #Move slightly closer to the target.
	return (current_colour, target_colour)
	
def _run(tank_chan_path, options):
	"""
	Sets up the game environment and runs the sandbox mainloop until the user
	generates a QUIT event or presses Escape.
	
	@type tank_chan_path: basestring
	@param tank_chan_path: The path to the ship descriptor file to use for both
	    the player and the debug target.
	@param options: A collection of parsed options, as returned by
	    C{optparse.OptionParser.parse_args()}.
	"""
	client_path = path_utils.getClientPath()
	
	#Process complex options.
	render_mode = _determineRenderMode(options)
	
	#Initialise constants.
	session_constants.init(30, 990, 550)
	session_constants.initVariables(5.0, 0.1)
	
	#Initialise PyGame and immediately required objects.
	pygame.init()
	
	#Initialise context.
	pygame.display.set_caption("Ultra Tank-chan! Development sandbox", "UTc! Sandbox")
	pygame.display.set_icon(pygame.image.load(client_path + 'assets' + os.sep + 'other' + os.sep + 'utc-icon.png'))
	surface = None
	if options.fsaa:
		try:
			pygame.display.gl_set_attribute(pygame.constants.GL_MULTISAMPLEBUFFERS, 1)
			surface = pygame.display.set_mode((session_constants.WIDTH, session_constants.HEIGHT), pygame.constants.OPENGL | pygame.constants.DOUBLEBUF)
		except:
			pygame.display.gl_set_attribute(pygame.constants.GL_MULTISAMPLEBUFFERS, 0)
			sys.stderr.write("Your system is incapable of FSAA.\n")
	if not surface:
		surface = pygame.display.set_mode((session_constants.WIDTH, session_constants.HEIGHT), pygame.constants.OPENGL | pygame.constants.DOUBLEBUF)
	clock = pygame.time.Clock()
	
	#Initialise ODE.
	ode_space = ode.Space()
	generic_rendering.init(options.debug_solid)
	
	#Initialise OpenGL.
	rendering.init()
	GL.glLightModeli(GL.GL_LIGHT_MODEL_TWO_SIDE, 0)
	GL.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, (0, 0, 1, 0))
	GL.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, (0.5, 0.5, 0.5, 1))
	GL.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, (0.5, 0.5, 0.5, 0))
	GL.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, (0.5, 0.5, 0.5, 0))
	GL.glEnable(GL.GL_LIGHT0)
	GL.glEnable(GL.GL_LIGHTING)
	GL.glEnable(GL.GL_NORMALIZE)
	GL.glEnable(GL.GL_COLOR_MATERIAL)
	GL.glEnable(GL.GL_DEPTH_TEST)
	
	#Set up the GL canvas.
	GL.glMatrixMode(GL.GL_PROJECTION)
	GL.glLoadIdentity()
	GLU.gluPerspective(45.0, float(session_constants.WIDTH) / session_constants.HEIGHT, 1, 1000)
	GL.glMatrixMode(GL.GL_MODELVIEW)
	
	#Initialise HUD elements.
	message_font = client_path + 'assets' + os.sep + 'other' + os.sep + 'Vera.ttf'
	stats_font= client_path + 'assets' + os.sep + 'other' + os.sep + 'DejaVuSansMono.ttf'
	hud.init((75, 75, 150),
	 pygame.font.Font(message_font, 14),
	 pygame.font.Font(stats_font, 12),
	 pygame.font.Font(stats_font, 14),
	 pygame.font.Font(stats_font, 16)
	)
	messages = hud.MessageBox()
	
	#Initialise background to a random pastel.
	background_rgb_current = [random.uniform(0.5, 0.95) for i in range(3)]
	(background_rgb_current, background_rgb_target) = _updateBackgroundColour(background_rgb_current, background_rgb_current)
	GL.glClearColor(background_rgb_current[0], background_rgb_current[1], background_rgb_current[2], 0.0)
	
	#Render loading screen.
	GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
	status_screen = hud.StatusScreen(client_path + 'assets' + os.sep + 'textures' + os.sep + 'screen-loading.png', (128, 164), (0, 0, 0))
	status_screen.setStatus("initialising environment")
	status_screen.render(messages)
	pygame.display.flip()
	
	#Set up the rendering environment.
	gameobjects.init()
	tankchan.init()
	effects.init()
	
	weapons.initBolts()
	weapons.initMissiles()
	
	#Initialise colours. (Can't happen earlier, for whatever reason)
	player_colours = [tuple([random.random() for i in range(3)]) for j in range(2)]
	team_colours = [tuple([random.random() for i in range(3)]) for j in range(2)]
	switch_to_basic = False
	try:
		session_constants.initColours(player_colours, team_colours, render_mode)
	except ValueError, e:
		if render_mode in (shading.RENDER_MODE_CEL, shading.RENDER_MODE_GRADIENT, shading.RENDER_MODE_FLAT):
			switch_to_basic = True
		else:
			raise e
	except OpenGL.error.NullFunctionError:
		switch_to_basic = True
		
	if switch_to_basic: #Shaders unavailable, though they were requested.
		sys.stderr.write("Your system does not appear to support shaders; switching to basic mode.\n")
		render_mode = shading.RENDER_MODE_BASIC
		session_constants.initColours(player_colours, team_colours, render_mode)
		
	#Update loading screen.
	GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
	status_screen.setStatus("loading objects")
	status_screen.render(messages)
	pygame.display.flip()
	
	#Load game objects.
	player = tankchan.TankChan(
	 tank_chan_path, render_mode,
	 "player", 0, random.randint(0, len(team_colours) - 1), random.choice((
	  constants.POWER_LOVE,
	  constants.POWER_STARLIGHT,
	  constants.POWER_MOE,
	  constants.POWER_RAINBOW,
	  constants.POWER_HAPPINESS
	 )),
	 ('00', '00', '00', '00'),
	 (1, 1, 1, 1),
	 ('00', '00', '00', '00'),
	 ode_space
	)
	
	#Set sandbox testing parameters.
	player.setPosition((0, 0, 0))
	player.setRotation((0, 0, 0))
	player._target_speed = 37.2
	player.damageShielding(player._shielding)
	player.lowerShields()
	player.damageHull(player._hull * .71)
	player.damageSubsystem(player._sensors_integrity * 0.1, tankchan.SUBSYSTEM_SENSORS)
	player.damageSubsystem(player._engines_integrity * 0.3, tankchan.SUBSYSTEM_ENGINES)
	player.damageSubsystem(player._shields_integrity * 0.6, tankchan.SUBSYSTEM_SHIELDS)
	player.damageSubsystem(player._weapons_integrity * 0.99, tankchan.SUBSYSTEM_WEAPONS)
	player._updateODEHitboxes(True)
	player._updateODEShielding()
	
	#Validate ODE bounding.
	reported_collisions = []
	def _collisionCallback(args, geom_1, geom_2):
		def _formatError(geom):
			style = None
			if type(geom) == ode.GeomBox:
				style = 'box'
			else:
				style = 'sphere'
			tank_chan = geom.tank_chan
			(position, rotation) = tank_chan._ode_geoms[geom]
			role = ('sensors', 'engines', 'shields', 'weapons')[geom.subsystem - 1]
			return "%s %s (p:%s, s:%s)" % (role, style, '/'.join(map(str, position)), '/'.join(map(str, geom.getLengths())))
			
		if not (geom_1, geom_2) in reported_collisions and ode.collide(geom_1, geom_2):
			print "ODE placement issue: %s touches %s" % (_formatError(geom_1), _formatError(geom_2))
			reported_collisions.append((geom_1, geom_2))
			reported_collisions.append((geom_2, geom_1))
	ode_space.collide(None, _collisionCallback)
	reported_collisions = None
	
	#Set remaining sandbox testing parameters.
	target = tankchan.TankChan(
	 tank_chan_path, render_mode,
	 "target", 1, 1, random.choice((
	  constants.POWER_LOVE,
	  constants.POWER_STARLIGHT,
	  constants.POWER_MOE,
	  constants.POWER_RAINBOW,
	  constants.POWER_HAPPINESS
	 )),
	 ('00', '00', '00', '00'), #None outside of sandbox.
	 (1, 1, 1, 1), #None outside of sandbox.
	 ('00', '00', '00', '00'), #None outside of sandbox.
	 ode_space
	)
	target.setPosition((0, 0, 20))
	target.setRotation((0, 180, 0))
	target._current_speed = target._target_speed = target._rated_speed
	target.damageHull(target._hull / 2)
	target.damageSubsystem(target._sensors_integrity * 0.6, tankchan.SUBSYSTEM_SENSORS)
	target.damageSubsystem(target._engines_integrity * 0.3, tankchan.SUBSYSTEM_ENGINES)
	target.damageSubsystem(target._weapons_integrity * 0.99, tankchan.SUBSYSTEM_WEAPONS)
	target.damageShielding(1000)
	target._updateODEHitboxes(True)
	target._updateODEShielding()
	
	#Establish gameplay element lists.
	tank_chans = [player, target]
	other_players = [target]
	hostiles = [other_player for other_player in other_players if other_player.isHostile(player)]
	visual_effects = []
	bolts = []
	ecms = []
	missiles = []
	field_obstacles = [
	 obstacles.Box(
	  ode_space,
	  (10, 0, 40),
	  (random.random() * 360.0, random.random() * 360.0, random.random() * 360.0),
	  (random.uniform(5.0, 10.0), random.uniform(5.0, 10.0), random.uniform(5.0, 10.0)),
	  (random.uniform(0.0, 0.5), random.uniform(0.0, 0.5), random.uniform(0.0, 0.5))
	 ),
	 obstacles.Sphere(
	  ode_space,
	  (-10, 0, 40),
	  (random.random() * 360.0, random.random() * 360.0, random.random() * 360.0),
	  random.uniform(2.5, 5.0),
	  (random.uniform(0.0, 0.5), random.uniform(0.0, 0.5), random.uniform(0.0, 0.5))
	 )
	]
	
	hud_screen = hud.HUDScreen(player)
	scoring = hud.Scoring(20, 0.0, 1, 0, 0)
	
	#Update the loading screen.
	GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
	status_screen.setStatus("performing sandbox tests")
	status_screen.render(messages)
	pygame.display.flip()
	
	#Initialise interface variables.
	rotate = move = False
	frame = 0
	cycle = 0
	camera_obj = camera.Camera(player.getPosition())
	
	#Set up positioning modifiers.
	player_dis_x = player_dis_y = player_dis_z = 0.0
	target_dis_x = target_dis_y = 0.0
	target_dis_z = 20.0
	
	#Kill the loading screen.
	GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
	status_screen.setStatus("synchronizing clock")
	status_screen.render(messages)
	pygame.display.flip()
	clock.tick(session_constants.FPS) #Ensure the FPS isn't skewed at the start, which would mess up movement.
	status_screen.destroy()
	del status_screen
	
	#Create variables for managing player death.
	death_screen = None
	death_time = 0.0
	
	while True:
		clock.tick(session_constants.FPS) #Attempt to maintain a constant framerate.
		
		if not target.isAlive() and not visual_effects:
			target.reset(target.getPosition(), target.getRotationEuler())
			
		#Update placement information, assuming there is any input.
		player_rot_x = player_rot_y = player_rot_z = 0.0
		target_rot_x = target_rot_y = target_rot_z = 0.0
		player_changed = target_changed = False
		for e in pygame.event.get():
			if e.type == pygame.constants.QUIT:
				sys.exit()
			elif e.type == pygame.constants.KEYDOWN:
				if e.key == pygame.constants.K_ESCAPE:
					if messages.isTextEntryInProgress():
						messages.abortTextEntry()
					else:
						sys.exit()
				elif e.key == pygame.constants.K_RETURN:
					if messages.isTextEntryInProgress():
						print messages.completeTextEntry()
					else:
						messages.beginTextEntry()
				else:
					if messages.isTextEntryInProgress() and (e.key == pygame.constants.K_BACKSPACE or 32 <= e.key <= 127):
						messages.addCharacter(e.unicode)
					elif player.isAlive():
						if e.key == pygame.constants.K_PAGEUP:
							target_changed = target_changed or True
							target_dis_z += 1
						elif e.key == pygame.constants.K_PAGEDOWN:
							target_changed = target_changed or True
							target_dis_z -= 1
						elif e.key == pygame.constants.K_KP5:
							player_changed = player_changed or True
							player.setPosition((0, 0, 0))
							player.setRotation((0, 0, 0))
							player_dis_x = player_dis_y = player_dis_z = 0.0
							camera_obj = camera.Camera(player.getPosition())
						elif e.key == pygame.constants.K_KP2:
							player_changed = player_changed or True
							player_rot_x -= 10
						elif e.key == pygame.constants.K_KP8:
							player_changed = player_changed or True
							player_rot_x += 10
						elif e.key == pygame.constants.K_KP4:
							player_changed = player_changed or True
							player_rot_y += 10
						elif e.key == pygame.constants.K_KP6:
							player_changed = player_changed or True
							player_rot_y -= 10
						elif e.key == pygame.constants.K_KP7:
							player_changed = player_changed or True
							player_rot_z -= 10
						elif e.key == pygame.constants.K_KP9:
							player_changed = player_changed or True
							player_rot_z += 10
						elif e.key == pygame.constants.K_KP1:
							player_changed = player_changed or True
							player_dis_z -= 1
						elif e.key == pygame.constants.K_KP3:
							player_changed = player_changed or True
							player_dis_z += 1
						elif e.key == pygame.constants.K_UP:
							player_changed = player_changed or True
							player_dis_y += 1
						elif e.key == pygame.constants.K_DOWN:
							player_changed = player_changed or True
							player_dis_y -= 1
						elif e.key == pygame.constants.K_LEFT:
							player_changed = player_changed or True
							player_dis_x += 1
						elif e.key == pygame.constants.K_RIGHT:
							player_changed = player_changed or True
							player_dis_x -= 1
						elif e.key == pygame.constants.K_SPACE:
							bolts += player.dischargeBolts(target)
						elif e.key == pygame.constants.K_PERIOD:
							player.getWeaponSystems()[0].cycleNext()
						elif e.key == pygame.constants.K_COMMA:
							player.getWeaponSystems()[0].cyclePrevious()
						elif e.key == pygame.constants.K_m and target.isAlive():
							missiles += player.dischargeMissile(target)
						elif e.key == pygame.constants.K_n:
							player.getWeaponSystems()[1].cycleNext()
						elif e.key == pygame.constants.K_b:
							player.getWeaponSystems()[1].cyclePrevious()
						elif e.key == pygame.constants.K_c:
							ecms += player.dischargeECM()
			elif e.type == pygame.constants.MOUSEBUTTONDOWN:
				target_changed = target_changed or True
				if e.button == 4:
					target_rot_z += 10
				elif e.button == 5:
					target_rot_z -= 10
				elif e.button == 1:
					rotate = True
				elif e.button == 3:
					move = True
			elif e.type == pygame.constants.MOUSEBUTTONUP:
				target_changed = target_changed or True
				if e.button == 1:
					rotate = False
				elif e.button == 3:
					move = False
				elif e.button == 2:
					target_changed = target_changed or True
					target.setPosition((0, 0, 20))
					target.setRotation((0, 180, 0))
					target_dis_x = target_dis_y = 0.0
					target_dis_z = 20.0
			elif e.type == pygame.constants.MOUSEMOTION:
				(i, j) = e.rel
				if rotate: #Invert X and Y to map to the mouse's 2D plane.
					target_changed = target_changed or True
					target_rot_x += j
					target_rot_y += i
				if move:
					target_changed = target_changed or True
					target_dis_x -= i / 20.0
					target_dis_y -= j / 20.0
		if player_changed:
			player.updateVectors(None, (player_rot_x, player_rot_y, player_rot_z))
			#Update the camera.
			camera_obj.rotate(player_rot_x, player_rot_y, player_rot_z)
		if target_changed:
			target.updateVectors(None, (target_rot_x, target_rot_y, target_rot_z))
			
		#Update elements.
		seconds = clock.get_time() / 1000.0
		if not player.isAlive():
			old_death_seconds = math.ceil(death_time)
			death_time -= seconds
			if death_time <= 0:
				player.reset(player.getPosition(), player.getRotationEuler())
				death_screen.destroy()
				death_screen = None
				hud_screen.reset()
			elif math.ceil(death_time) < old_death_seconds:
				death_screen.setStatus("resurrection in %i..." % math.ceil(death_time))
				
		for tank_chan in tank_chans:
			tank_chan.tick(seconds)
		messages.tick(seconds)
		scoring.tick(seconds)
		visual_effects = [effect for effect in visual_effects if effect.tick(seconds)]
		bolts = [bolt for bolt in bolts if bolt.tick(seconds)]
		ecms = [ecm for ecm in ecms if ecm.tick(seconds)]
		
		#Forcibly prohibit autonomous movement for sandbox purposes.
		player.setPosition((player_dis_x, player_dis_y, player_dis_z))
		camera_obj.setPosition((player_dis_x, player_dis_y, player_dis_z))
		target.setPosition((target_dis_x, target_dis_y, target_dis_z))
		target._current_speed = target._target_speed = target._rated_speed
		
		#Missiles need to be updated after the position reset.
		missiles = [missile for missile in missiles if missile.tick(seconds, [])]
		
		#Handle collisions.
		dead_elements = []
		kills = []
		fallen_shields = []
		ode_space.collide((visual_effects, [], dead_elements, kills, fallen_shields, player), collisions.collisionHandler)
		for (element, struck_element, struck_subsystem) in dead_elements:
			element.destroy()
		if not death_screen and [None for kill in kills if kill[1] == player]:
			#If (killer, victim) and (victim, killer) are both in kills, consider it a collision suicide.
			messages.addMessage("Kill", "%s crashed into %s" % (player.getName(), target.getName()), True)
			death_screen = hud.getDeathScreen()
			death_time = session_constants.RESURRECTION_TIME
			death_screen.setStatus("killed by <killer's name>")
		for (killer, victim) in kills:
			victim.explode()
		for tank_chan in fallen_shields:
			tank_chan.lowerShields()
			
		#Clear for the next frame.
		GL.glClearColor(background_rgb_current[0], background_rgb_current[1], background_rgb_current[2], 0.0)
		GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
		
		if player.isAlive():
			#Set the camera.
			camera_transformation_matrix = camera_obj.getTransformationMatrix()
			camera_obj.setView()
			
			#Render weapons.
			if render_mode == shading.RENDER_MODE_WIREFRAME:
				GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE)
			else:
				GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL)
				
			#Draw bolts.
			GL.glDisable(GL.GL_LIGHTING)
			for bolt in bolts:
				bolt.updateCameraVectors(camera_transformation_matrix, False)
				bolt.render()
			GL.glEnable(GL.GL_LIGHTING)
			
			#Draw missiles.
			for missile in missiles:
				missile.updateCameraVectors(camera_transformation_matrix, True)
				missile.render()
				
			#Render the obstacles.
			GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL)
			for obstacle in field_obstacles:
				obstacle.updateCameraVectors(camera_transformation_matrix, False)
				obstacle.render()
			GL.glShadeModel(GL.GL_FLAT)
			
			#Render the other players.
			for tank_chan in other_players:
				tank_chan.updateCameraVectors(camera_transformation_matrix, True)
				tank_chan.render(
				 not tank_chan.isHostile(player),
				 options.rnd_hud and player.getSubsystemCondition(tankchan.SUBSYSTEM_SENSORS) >= tankchan.SUBSYSTEM_CONDITION_CRIPPLED,
				 player.getSubsystemCondition(tankchan.SUBSYSTEM_SENSORS) >= tankchan.SUBSYSTEM_CONDITION_FUNCTIONAL,
				 player.getSubsystemCondition(tankchan.SUBSYSTEM_SENSORS) >= tankchan.SUBSYSTEM_CONDITION_FAIR,
				 True, False
				)
				
			#Draw visual effects.		
			#Set up 2D rendering parameters.
			GL.glEnable(GL.GL_DEPTH_TEST)
			GL.glDisable(GL.GL_LIGHTING)
			GL.glEnable(GL.GL_TEXTURE_2D)
			GL.glEnable(GL.GL_BLEND)
			GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA)
			GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL)
			GL.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR)
			GL.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR)
			GL.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE)
			
			for effect in reversed(visual_effects):
				effect.updateCameraVectors(camera_transformation_matrix, False)
				effect.render()
				
			#Restore 3D rendering parameters.
			GL.glDisable(GL.GL_BLEND)
			GL.glDisable(GL.GL_TEXTURE_2D)
			GL.glEnable(GL.GL_LIGHTING)
			
			#Render sandbox visuals.
			if options.hitboxes:
				target._updateODEHitboxes(True)
				target._updateODEShielding()
				ode_rendering.renderODE([geom for (geom, (position, rotation_matrix)) in target._ode_geoms.iteritems() if geom.subsystem == tankchan.SUBSYSTEM_ENGINES], 0.5, 0.0, 0.75)
				ode_rendering.renderODE([geom for (geom, (position, rotation_matrix)) in target._ode_geoms.iteritems() if geom.subsystem == tankchan.SUBSYSTEM_SENSORS], 0.0, 0.5, 0.75)
				ode_rendering.renderODE([geom for (geom, (position, rotation_matrix)) in target._ode_geoms.iteritems() if geom.subsystem == tankchan.SUBSYSTEM_SHIELDS], 0.375, 0.75, 1.0)
				ode_rendering.renderODE([geom for (geom, (position, rotation_matrix)) in target._ode_geoms.iteritems() if geom.subsystem == tankchan.SUBSYSTEM_WEAPONS], 0.25, 0.5, 1.0)
			if options.weapons:
				for (unit, enabled) in target._bolt_system._units:
					for bolt in unit._banks:
						GL.glPushMatrix()
						generic_rendering.initMatrixEuler(bolt.getPosition(), (0, 0, 0))
						generic_rendering.renderSphere(0.125, 1.0, 0.0, 0.0)
						GL.glPopMatrix()
				for (missile, enabled) in target._missile_system._units:
					GL.glPushMatrix()
					generic_rendering.initMatrixEuler(missile.getPosition(), (0, 0, 0))
					generic_rendering.renderSphere(0.125, 0.0, 1.0, 0.0)
					GL.glPopMatrix()
					
			#Render HUD.
			if options.rnd_hud:
				if target.isAlive():
					hud_screen.update(other_players, missiles, hostiles, target)
				else:
					hud_screen.update(other_players, missiles, hostiles, None)
				hud_screen.render(messages, scoring)
		else:
			death_screen.render(messages)
			
		#Display the new frame.
		pygame.display.flip()
		
		#Change the background colour slightly.
		(background_rgb_current, background_rgb_target) = _updateBackgroundColour(background_rgb_current, background_rgb_target)
		
		#Update the frame-state data. (May not be needed in production)
		frame += 1
		if frame == session_constants.FPS:
			frame = 0
			cycle += 1
			if not cycle % 5:
				messages.addMessage("FPS", str(clock.get_fps()), False)
				
if __name__ == '__main__':
	#Handle commandline arguments
	parser = optparse.OptionParser(usage="%prog [options] [model file|menu number]", version="%s v%s" % ("Ultra Tank-chan! Development sandbox", "stable"),
	 description="The sandbox is an environment in which UTc! elements may be localy manipulated for development purposes.")
	parser.add_option("-w", "--wireframe", dest="wireframe", help="Render objects using wireframe vectors", action="store_true", default=False)
	parser.add_option("-p", "--plain", dest="plain", help="Disable GLSL/ARB shading (for primitive cards and MESA)", action="store_true", default=False)
	parser.add_option("-g", "--gradient", dest="gradient", help="Use smooth shading instead of celshading", action="store_true", default=False)
	parser.add_option("-f", "--flat", dest="flat", help="Render player and team colours as 2D surfaces", action="store_true", default=False)
	parser.add_option("--fsaa", action="store_true", dest="fsaa", help="Specify full-screen anti-aliasing passes", default=False)
	group = optparse.OptionGroup(parser, "Metainformation-rendering options", "These options render regions that affect gameplay and physics")
	group.add_option("-u", "--hud", dest="rnd_hud", help="Render HUD elements", action="store_true", default=False)
	group.add_option("-s", "--solid", dest="debug_solid", help="Render ODE and positional elements as solid polygons", action="store_true", default=False)
	group.add_option("--hitboxes", dest="hitboxes", help="Render hitboxes", action="store_true", default=False)
	group.add_option("--weapons", dest="weapons", help="Render weapon mounpoints", action="store_true", default=False)
	parser.add_option_group(group)
	(options, arguments) = parser.parse_args()
	del group
	
	if len([opt for opt in [options.wireframe, options.plain, options.gradient, options.flat] if opt]) > 1:
		parser.error('rendering modes are mutually exclusive')
	del parser
	
	if arguments: #Skip the menu.
		if not arguments[0].isdigit(): #It's the path to a model descriptor.
			_run(arguments[0], options)
		else: #It's a model index ID.
			_run(_chooseTankChan(int(arguments[0])), options)
	else: #Display the menu.
		_run(_chooseTankChan(), options)
		